package io.gitee.zhangbinhub.acp.core.common.base

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator
import io.gitee.zhangbinhub.acp.core.common.exceptions.ConfigException
import io.gitee.zhangbinhub.acp.core.common.log.LogFactory
import io.gitee.zhangbinhub.acp.core.common.tools.CommonUtils
import java.io.*
import java.util.concurrent.ConcurrentHashMap

abstract class BaseXml {
    private var fileAbsPathName: String = ""
    private var lastModified: Long = 0

    /**
     * 转换成 XML 字符串
     *
     * @return XML 字符串
     */
    @Throws(ConfigException::class)
    fun toXMLString(): String = try {
        synchronized(this) {
            val xmlMapper = getMapper()
            xmlMapper.writeValueAsString(this)
        }
    } catch (e: Exception) {
        log.error(e.message)
        throw ConfigException("convert config failed:[" + this.javaClass.canonicalName + "]")
    }

    /**
     * 将java对象信息写入xml文件
     */
    @Throws(ConfigException::class)
    fun storeToXml() {
        var oFile: OutputStreamWriter? = null
        try {
            if (!CommonUtils.isNullStr(fileAbsPathName)) {
                synchronized(this) {
                    val file = File(fileAbsPathName)
                    val xmlMapper = getMapper()
                    oFile = OutputStreamWriter(FileOutputStream(file), CommonUtils.defaultCharset)
                    xmlMapper.writeValue(oFile, this)
                    lastModified = file.lastModified()
                }
                log.info("write [" + fileAbsPathName + "] success => " + this.javaClass.canonicalName)
            } else {
                throw ConfigException("write config failed: need specify XML file for " + this.javaClass.canonicalName)
            }
        } catch (e: Exception) {
            log.error(e.message)
            throw ConfigException("write config failed:[$fileAbsPathName]")
        } finally {
            if (oFile != null) {
                try {
                    oFile!!.close()
                } catch (e: IOException) {
                    log.error(e.message, e)
                }
            }
        }
    }

    fun getFileAbsPathName(): String {
        return fileAbsPathName
    }

    fun setFileAbsPathName(fileAbsPathName: String) {
        this.fileAbsPathName = fileAbsPathName
    }

    companion object {
        private val log = LogFactory.getInstance(BaseXml::class.java) // 日志对象
        private val instanceMap: MutableMap<String, BaseXml> = ConcurrentHashMap()

        private fun getMapper(): XmlMapper {
            val builder = XmlMapper.builder()
            builder.serializationInclusion(JsonInclude.Include.NON_NULL)
            builder.propertyNamingStrategy(PropertyNamingStrategy())
            builder.enable(SerializationFeature.INDENT_OUTPUT)
            builder.enable(MapperFeature.USE_STD_BEAN_NAMING)
            builder.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)
            builder.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
            builder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            return builder.build()
        }

        /**
         * 序列化xml文件为java对象
         *
         * @param cls 序列化后转换的java类
         * @return 实例对象
         */
        @JvmStatic
        @Throws(ConfigException::class)
        protected fun load(cls: Class<out BaseXml?>): BaseXml? {
            var fileName: String? = null
            return try {
                fileName = CommonUtils.getProperties(cls.canonicalName, "")
                if (!CommonUtils.isNullStr(fileName)) {
                    val file = File(CommonUtils.getAbsPath(fileName))
                    var inputStreamReader: InputStreamReader? = null
                    if (!file.exists()) {
                        val input = CommonUtils.getResourceInputStream(fileName.replace("\\", "/"))
                        if (input != null) {
                            inputStreamReader = InputStreamReader(input, CommonUtils.defaultCharset)
                        } else {
                            log.error("$fileName is not found")
                        }
                    } else {
                        inputStreamReader = InputStreamReader(FileInputStream(file), CommonUtils.defaultCharset)
                    }
                    val instance = instanceMap[fileName]
                    if (!instanceMap.containsKey(fileName) || file.exists() && file.lastModified() > instance!!.lastModified) {
                        if (inputStreamReader != null) {
                            synchronized(instanceMap) {
                                val xmlMapper = getMapper()
                                val obj = xmlMapper.readValue(inputStreamReader, cls)
                                obj?.apply {
                                    this.fileAbsPathName = CommonUtils.getAbsPath(fileName)
                                    if (file.exists()) {
                                        this.lastModified = file.lastModified()
                                    }
                                    instanceMap[fileName] = this
                                    log.info("load [" + fileName + "] success => " + cls.canonicalName)
                                }
                                return obj
                            }
                        } else {
                            null
                        }
                    } else {
                        instance
                    }
                } else {
                    log.warn("load config failed: need specify XML file for " + cls.canonicalName)
                    null
                }
            } catch (e: Exception) {
                log.error(e.message, e)
                throw ConfigException("load config failed:[$fileName]")
            }
        }
    }
}